# coding=utf-8
import Queue
import binascii
import struct
from ctypes import create_string_buffer
from time import sleep, time

from bluepy import btle
from bluepy.btle import DefaultDelegate, UUID, Peripheral, Scanner, BTLEException

BUF_SIZE = 10


class Command(object):
    header = 0x68
    tail = 0x16

    def __init__(self, command_id, data):
        self.command_id = command_id
        self.data = bytearray.fromhex(data)
        self.length = len(data) / 2

    def to_frame(self):
        b = bytearray()
        b.append(Command.header)
        b.append(self.command_id)
        b.extend(struct.pack('>Bb', self.length, 0x00))
        b.extend(self.data)
        _buffer = create_string_buffer(4)

        struct.pack_into('>i', _buffer, 0, sum(b))
        b.append(_buffer[3])
        b.append(Command.tail)
        return ''.join(format(x, '02x') for x in b)


class Response(object):
    header = 0x68
    tail = 0x16

    def __init__(self, raw_hexstring):
        self.raw_bytearray = bytearray.fromhex(raw_hexstring)
        # 虽然长度是两个字节，但是因为规范中总包长度不超过256，所以data内容不可能超过255，
        # 只需要读第三位（长度字段低位在前）的unsigned byte，就可以认为是全部长度了
        if len(self.raw_bytearray) > 6:
            length = self.raw_bytearray[2]
            self.ret = self.raw_bytearray[4:4 + length]
        else:
            self.ret = 0x00

    '''虚方法，留给子类实现'''

    def get_data(self):
        pass


class HeartBeatResponse(Response):

    def __init__(self, raw_hexstring):
        Response.__init__(self, raw_hexstring)
        self.heart_beat = 0
        self.peace_count = 0
        self.meter_count = 0
        self.calorie = 0
        self.peace_speed = 0

    def get_data(self):
        if self.ret != 0x00:
            # 步数、里程、卡路里都是四个byte的int，而且是小端序，即低位在头部字节，高位在尾部字节
            # 需要unpack按照小端序的方式解码
            # 心跳和跑步速率只有一个byte，无所谓端序
            _, self.heart_beat, self.peace_count, self.meter_count, self.calorie, self.peace_speed = \
                struct.unpack(
                    '<BBIIIB', self.ret)
        return self.heart_beat, self.peace_count, self.meter_count, self.calorie, self.peace_speed


class HandlerDelegate(DefaultDelegate):
    # Constructor (run once on startup)
    def __init__(self, queue, rx_handle):
        DefaultDelegate.__init__(self)
        self.queue = queue
        self.rx_handle = rx_handle
        self.cache = bytearray()

    # func is caled on notifications
    def handleNotification(self, cHandle, data):
        if cHandle == self.rx_handle:
            b = bytearray()
            b.extend(data)
            # 0x86开头/0x16结尾的才是完整的数据帧
            if b[0] == 0x68 and b[-1] == 0x16:
                # 完整数据，直接进队列
                self.queue.put(binascii.hexlify(b))
            elif b[0] == 0x68:
                # 数据分包不完整，先压入缓存等待下次处理
                self.cache = b
            elif b[-1] == 0x16:
                self.cache.extend(b)
                self.queue.put(binascii.hexlify(self.cache))
                # 重置cache
                self.cache = bytearray()
            else:
                # 不符合要求的数据
                pass
        else:
            # print u'非接收渠道的消息 {}'.format(binascii.hexlify(data))
            pass


class BraceletMainService(object):
    service_uuid = UUID("FFF0")
    rx_char_uuid = UUID("FFF1")
    tx_char_uuid = UUID("FFF2")

    def __init__(self, address, add_type, name='default'):
        self._q = Queue.Queue(BUF_SIZE)
        self.cycle_flag = True
        self._service = None
        self._rx_ch = None
        self._tx_ch = None
        self._peripheral = None
        self._address = address
        self._add_type = add_type
        self.name = name

    def conn(self):
        self._peripheral = Peripheral(self._address, self._add_type)
        print 'bracelet {} {} connected '.format(self._address, self._add_type)
        self._setup()

    def _setup(self):
        self._service = self._peripheral.getServiceByUUID(BraceletMainService.service_uuid)

        self._rx_ch = self._service.getCharacteristics(BraceletMainService.rx_char_uuid)[0]
        hnd = self._rx_ch.getHandle()
        self._peripheral.setDelegate(HandlerDelegate(self._q, hnd))

        self._peripheral.writeCharacteristic(hnd + 1, struct.pack('<bb', 0x01, 0x00))
        self._tx_ch = self._service.getCharacteristics(BraceletMainService.tx_char_uuid)[0]

    def _get_resp(self):
        while True:
            if self._peripheral.waitForNotifications(1):
                try:
                    resp = self._q.get_nowait()
                    return resp
                except Queue.Empty:
                    pass
            else:
                print "Waiting... Waited more than one sec for notification"

    def battery(self):
        # tx_ch.write(struct.pack('<bbbbbb', 0x68, 0x03, 0x00, 0x00, 0x6B, 0x16))
        command = Command(0x03, '')
        self._tx_ch.write(command.to_frame().decode('hex'))
        # val = binascii.b2a_hex(tx_ch.read())
        # print ("0x" + val)
        return self._get_resp()

    def set_config(self, item=0x01, flag=0x01):
        b = bytearray()
        b.append(0x00)
        b.append(item)
        b.append(flag)
        command = Command(0x05, binascii.hexlify(b))
        print command.to_frame()
        self._tx_ch.write(command.to_frame().decode('hex'))
        return self._get_resp()

    def noticall(self, caller='', name=''):
        b = bytearray()
        b.append(0x00)
        b.extend(caller)
        while len(b) <= 15:
            b.append(0x00)
        b.extend(name.encode('utf-8'))
        command = Command(0x01, binascii.hexlify(b))
        print command.to_frame()
        self._tx_ch.write(command.to_frame().decode('hex'))
        return self._get_resp()

    def set_peace_param(self):
        self._tx_ch.write('68040400B23C001C7A16'.decode('hex'))
        return self._get_resp()

    def get_heartbeat(self):
        command = Command(0x06, '00')
        self._tx_ch.write(command.to_frame().decode('hex'))
        resp = HeartBeatResponse(self._get_resp())
        return resp

    def open_heartbeat_dect(self):
        command = Command(0x06, '01')
        self._tx_ch.write(command.to_frame().decode('hex'))
        return self._get_resp()

    def close_heartbeat_dect(self):
        command = Command(0x06, '02')
        self._tx_ch.write(command.to_frame().decode('hex'))
        return self._get_resp()

    def find_start(self):
        command = Command(0x13, '00')
        self._tx_ch.write(command.to_frame().decode('hex'))
        return self._get_resp()

    def find_end(self):
        command = Command(0x13, '01')
        self._tx_ch.write(command.to_frame().decode('hex'))
        return self._get_resp()

    def set_current_time(self):

        mktime_sec = int(time())
        buffer = create_string_buffer(4)
        struct.pack_into('<I', buffer, 0, mktime_sec + 8 * 3600)

        command = Command(0x20, binascii.hexlify(buffer))
        self._tx_ch.write(command.to_frame().decode('hex'))
        return self._get_resp()

    def clear_data(self):
        command = Command(0x11, '01')
        self._tx_ch.write(command.to_frame().decode('hex'))
        return self._get_resp()

    '''
    持续的测量心跳、步数、里程、消耗卡路里和跑步速度
    
    '''

    def detect(self, socketio):
        # 先清空原来的运动数据
        self.clear_data()
        self.open_heartbeat_dect()
        while self.cycle_flag:
            try:
                r = self.get_heartbeat().get_data()
                print '心跳{} 步数{} 里程{}米 卡路里{} 步速{}'.format(r[0], r[1], r[2], r[3], r[4])
                if socketio:
                    socketio.emit('server_response',
                                  {'heart_beat': r[0], 'peace_count': r[1],
                                   'meter_count': r[2], 'calorie': r[3],
                                   'peace_speed': r[4], 'data': u"运动中",
                                   'bracelet_name': self.name},
                                  namespace='/test')
                    socketio.sleep(1)
                else:
                    sleep(1)
            except BTLEException:
                try:
                    self.conn()
                except BTLEException:
                    print '连接不上，休息一秒再尝试'
                    if socketio:
                        socketio.sleep(1)
                    else:
                        sleep(1)
        self.close_heartbeat_dect()

    @staticmethod
    def scan():
        d = {}
        scanner = Scanner()
        bracelet_item = None
        while bracelet_item is None:
            devices = scanner.scan(10.0)
            for dev in devices:
                # print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)
                for (adtype, desc, value) in dev.getScanData():
                    if desc == 'Manufacturer':
                        d['Manufacturer'] = value
                    if desc == "Complete Local Name":
                        d['CompleteLocalName'] = value
                    print ("{} {}".format(desc, value))
                print '-------------'
                if 'CompleteLocalName' in d.keys() and d['CompleteLocalName'].startswith('K18S'):
                    print 'find you! %s %s ' % (dev.addr, dev.addrType)
                    bracelet_item = dev
                    break
                print 'bracelet_item not in range, continue'
        return BraceletMainService(bracelet_item.addr, bracelet_item.addrType)


if __name__ == "__main__":
    btle.Debugging = False
    # h = HeartBeatResponse('68860f00004b740000005800000035000000004916')
    # print h.get_data()
    # bracelet = BraceletK18s.scan()
    # battery = Command(command_id=0x03, data='')
    # print battery.to_frame()

    bracelet = BraceletMainService('e2:c9:18:4f:8f:d9', 'random')
    bracelet.conn()
    r = bracelet.get_heartbeat().get_data()
    print bracelet.clear_data()
    # print bracelet.battery()
    # print bracelet.noticall("13645168120", u"曹祖鹏")
    # print bracelet.set_config(0x06, 0x00)
    # bracelet.detect()
    # bracelet.find_start()
    # sleep(3)
    # bracelet.find_end()
    # print bracelet.set_current_time()
